﻿using System;
using System.IO;
using System.Collections.Generic;
using Winterdom.BizTalk.PipelineTesting;
using System.Collections.ObjectModel;
using BizUnit.Common;
using BizUnit.Xaml;
using BizUnit.TestSteps.BizTalk.Pipeline;
using BizUnit;
using BizUnit.TestSteps.BizTalk;
using System.Xml;
using System.Xml.Serialization;

namespace BREPipelineFramework.CustomBizUnitTestSteps
{
    /// <summary>
    /// This test step class uses an adjusted implementation of the BizTalkMessageInfoFactory class which caters to the existence of null context properties
    /// on a message and ignores them rather than throwing a NullReferenceException.
    /// </summary>
    public class ExecuteReceivePiplineWithNullablePropertyStep : TestStepBase
    {
        private string _pipelineAssemblyPath;
        private string _pipelineTypeName;
        private Collection<DocSpecDefinition> _docSpecsRawList = new Collection<DocSpecDefinition>();
        private Type[] _docSpecs;
        private string _instanceConfigFile;
        private string _source;
        private string _sourceEncoding;
        private string _destinationDir;
        private string _destinationFileFormat;
        private string _inputContextFile;
        private string _outputContextFileFormat;
        private DataLoaderBase instanceConfigLoader;
        private DataLoaderBase inputContextLoader;

        ///<summary>
        /// Gets and sets the assembly path for the .NET type of the pipeline to be executed
        ///</summary>
        public string PipelineAssemblyPath
        {
            get { return _pipelineAssemblyPath; }
            set { _pipelineAssemblyPath = value; }
        }

        ///<summary>
        /// Gets and sets the type name for the .NET type of the pipeline to be executed
        ///</summary>
        public string PipelineTypeName
        {
            get { return _pipelineTypeName; }
            set { _pipelineTypeName = value; }
        }

        ///<summary>
        /// Gets and sets the docspecs for the pipeline to be executed. Pairs of typeName, assemblyPath.
        ///</summary>
        public Collection<DocSpecDefinition> DocSpecs
        {
            get { return _docSpecsRawList; }
            private set { _docSpecsRawList = value; }
        }

        ///<summary>
        /// Gets and sets the pipeline instance configuration for the pipeline to be executed
        ///</summary>
        public string InstanceConfigFile
        {
            get { return _instanceConfigFile; }
            set { _instanceConfigFile = value; }
        }

        ///<summary>
        /// Gets and sets the source file path for the input file to the pipeline
        ///</summary>
        public string Source
        {
            get { return _source; }
            set { _source = value; }
        }

        ///<summary>
        /// Gets and sets the source encoding
        ///</summary>
        public string SourceEncoding
        {
            get { return _sourceEncoding; }
            set { _sourceEncoding = value; }
        }

        ///<summary>
        /// Gets and sets the destination of the pipeline output
        ///</summary>
        public string DestinationDir
        {
            get { return _destinationDir; }
            set { _destinationDir = value; }
        }

        ///<summary>
        /// Gets and sets the destination file format 
        ///</summary>
        public string DestinationFileFormat
        {
            get { return _destinationFileFormat; }
            set { _destinationFileFormat = value; }
        }

        ///<summary>
        /// Gets and sets the message context for the input message
        ///</summary>
        public string InputContextFile
        {
            get { return _inputContextFile; }
            set { _inputContextFile = value; }
        }

        ///<summary>
        /// Gets and sets the message context file format for the output message
        ///</summary>
        public string OutputContextFileFormat
        {
            get { return _outputContextFileFormat; }
            set { _outputContextFileFormat = value; }
        }

        public DataLoaderBase InstanceConfigLoader
        {
            get { return instanceConfigLoader; }
            set { instanceConfigLoader = value; }
        }

        public DataLoaderBase InputContextLoader
        {
            get { return inputContextLoader; }
            set { inputContextLoader = value; }
        }

        /// <summary>
        /// TestStepBase.Execute() implementation
        /// </summary>
        /// <param name='context'>The context for the test, this holds state that is passed beteen tests</param>
        public override void Execute(Context context)
        {
            try
            {
                if (_docSpecsRawList.Count > 0)
                {
                    var ds = new List<Type>(_docSpecsRawList.Count);
                    foreach (var docSpec in _docSpecsRawList)
                    {
                        var ass = AssemblyHelper.LoadAssembly((string)docSpec.AssemblyPath);
                        context.LogInfo("Loading DocumentSpec {0} from location {1}.", docSpec.TypeName, ass.Location);
                        var type = ass.GetType(docSpec.TypeName);

                        ds.Add(type);
                    }
                    _docSpecs = ds.ToArray();
                }

                context.LogInfo("Loading pipeline {0} from location {1}.", _pipelineTypeName, _pipelineAssemblyPath);
                var pipelineType = ObjectCreator.GetType(_pipelineTypeName, _pipelineAssemblyPath);

                var pipelineWrapper = PipelineFactory.CreateReceivePipeline(pipelineType);

                if (!string.IsNullOrEmpty(_instanceConfigFile))
                {
                    pipelineWrapper.ApplyInstanceConfig(_instanceConfigFile);
                }

                if (instanceConfigLoader != null)
                {
                    XmlReader reader = XmlReader.Create(instanceConfigLoader.Load(context));
                    pipelineWrapper.ApplyInstanceConfig(reader);   
                }

                if (null != _docSpecs)
                {
                    foreach (Type docSpec in _docSpecs)
                    {
                        pipelineWrapper.AddDocSpec(docSpec);
                    }
                }

                MessageCollection mc = null;
                using (Stream stream = new FileStream(_source, FileMode.Open, FileAccess.Read))
                {
                    var inputMessage = MessageHelper.CreateFromStream(stream);
                    if (!string.IsNullOrEmpty(_sourceEncoding))
                    {
                        inputMessage.BodyPart.Charset = _sourceEncoding;
                    }

                    // Load context file, add to message context.
                    if (!string.IsNullOrEmpty(_inputContextFile))
                    {
                        var mi = MessageInfo.Deserialize(_inputContextFile);
                        mi.MergeIntoMessage(inputMessage);
                    }

                    if (inputContextLoader != null)
                    {
                        XmlSerializer messageInfoSerializer = new XmlSerializer(typeof(MessageInfo));
                        XmlReader reader = XmlReader.Create(inputContextLoader.Load(context));
                        MessageInfo mi = messageInfoSerializer.Deserialize(reader) as MessageInfo;
                        mi.MergeIntoMessage(inputMessage);
                    }

                    mc = pipelineWrapper.Execute(inputMessage);
                }

                for (var count = 0; count < mc.Count; count++)
                {
                    string destination = null;
                    if (!string.IsNullOrEmpty(_destinationFileFormat))
                    {
                        destination = string.Format(_destinationFileFormat, count);
                        if (!string.IsNullOrEmpty(_destinationDir))
                        {
                            destination = Path.Combine(_destinationDir, destination);
                        }

                        PersistMessageHelper.PersistMessage(mc[count], destination);
                    }

                    if (!string.IsNullOrEmpty(_outputContextFileFormat))
                    {
                        var contextDestination = string.Format(_outputContextFileFormat, count);
                        if (!string.IsNullOrEmpty(_destinationDir))
                        {
                            contextDestination = Path.Combine(_destinationDir, contextDestination);
                        }

                        var mi = BizTalkMessageInfoFactory.CreateMessageInfo(mc[count], destination);
                        MessageInfo.Serialize(mi, contextDestination);
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// TestStepBase.Validate() implementation
        /// </summary>
        /// <param name='context'>The context for the test, this holds state that is passed beteen tests</param>
        public override void Validate(Context context)
        {
            ArgumentValidation.CheckForEmptyString(_pipelineTypeName, "pipelineTypeName");
            // pipelineAssemblyPath - optional

            _source = context.SubstituteWildCards(_source);
            if (!new FileInfo(_source).Exists)
            {
                throw new ArgumentException("Source file does not exist.", _source);
            }

            // destinationDir - optional
            if (!string.IsNullOrEmpty(_destinationDir))
            {
                _destinationDir = context.SubstituteWildCards(_destinationDir);
            }
        }
    }

}
